use std::marker::PhantomData;

use align::AlignedAddr4KB;
use arch_x86_64::cr::{
    cr3::{
        fields::{self as cr3_fields},
        Cr3,
    },
    cr4::{fields as cr4_fields, Cr4},
};
use register::{RegisterBufferReader, RegisterBufferWriter};

/// ### 处理器上下文
///
/// ⚠️ 注意：仅能在 Long 模式下使用。
///
/// 应当避免重复创建本结构体的实例，而是将其作为参数传递给其他有需要的函数。
///
/// 本结构体的实例并不占用空间，所以作为参数传递并不会产生消耗。
pub struct ProcessorContext {
    private: PhantomData<u32>,
}
impl ProcessorContext {
    /// 使能 [`PCIDE`](cr4_fields::PCIDE) 并且返回一个 ProcessorContext 的实例。
    ///
    /// 非安全函数，因为只能在 Long 模式下使能 ProcessorContext，
    /// 需要其余手段确保处于 Long 模式下。
    #[inline]
    pub unsafe fn enable() -> Self {
        Cr4::buffer().write::<cr4_fields::PCIDE>(true).flush();
        Self {
            private: PhantomData,
        }
    }
    /// 禁用后，TLB 中为当前逻辑处理器缓存的所有 cache 条目都会失效。
    /// 且所有 TLB 映射都与 PCID = 0 相关联。
    #[inline]
    pub unsafe fn disable(self) {
        Cr4::buffer().write::<cr4_fields::PCIDE>(false).flush();
    }
    /// ## 关于安全性
    ///
    /// 1. id 是 [`ProcessorContext`] 的方法，
    /// 而 ProcessorContext 仅可以通过 [`enable`](ProcessorContext::enable) 来实例化，实例化时会自动使能 [`CR4::PCIDE`](cr4_fields::PCIDE)。
    /// 3. ProcessorContext 通过 [`disable`](ProcessorContext::disable) 禁用后，用于所有权转让，将无法再使用之前实例。
    ///
    /// 综上，本函数在 ProcessorContext 环境下是安全的。
    #[inline]
    pub fn id(&mut self, identifer: u16) -> ProcessorContextAddrSpace {
        unsafe {
            Cr3::buffer()
                .as_pcid()
                .write::<cr3_fields::PCID>(identifer)
                .flush()
        }
        ProcessorContextAddrSpace {
            private: PhantomData,
        }
    }
    /// 获取当前 id
    pub fn current_id(&self) -> u16 {
        // PCID 为 12 bit，这里不会 panic 溢出。
        unsafe { Cr3::buffer().as_pcid().read::<cr3_fields::PCID>() }
    }
    pub fn current_addr_space(&self) -> AlignedAddr4KB<usize> {
        let base = unsafe { Cr3::buffer().read::<cr3_fields::TBA>() };
        AlignedAddr4KB::<usize>::from_base(base)
    }
}

pub struct ProcessorContextAddrSpace {
    private: PhantomData<usize>,
}
impl ProcessorContextAddrSpace {
    /// 设置 [`TBA`]，这将导致地址空间的切换。
    #[inline]
    pub fn addr_space(self, tba: AlignedAddr4KB<usize>) {
        unsafe { Cr3::buffer().write::<cr3_fields::TBA>(tba.base()).flush() }
    }
}
